# -*- coding: utf-8 -*-
import requests
import json
import sys
import os
from datetime import date
import pandas as pd
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from stock_info import StockInfo
from config import Config

def __search_stock_data(market_code: str=1, stock_code: str="601600", data_limit: str = '58', kline_type: str = '101', response_format: str = '1'):
    """
    查询单只股票数据 - 数据来源 东方财富网
    
    参数:
        market_code (str): 交易所代码
        stock_code (str): 股票代码
        data_limit (str): 数据条数, 默认 58 
        kline_type (str): K线类型, 默认 101
        response_format (str): 复权类型，默认 1
    返回:
        str: 生成的Markdown表格字符串？？待修改
    """
    # K线类型 映射 map
    kline_map = {
        '101': '日线',
        '102': '周线', 
        '103': '月线',
        '104': '季线',
        '105': '半年线',
        '106': '年线',
        '5': '5分钟',
        '15': '15分钟',
        '30': '30分钟',
        '60': '60分钟'
    }
    # 复权类型 映射 map
    response_format_map = {
        '0': '不复权',
        '1': '前复权', 
        '2': '后复权'
    }
    # 交易所代码 映射 map
    market_code_map = {
        '0': '深交所',
        '1': '上交所',
        '116': '港股',
        '105': '美股-纳斯达克',
        '106': '美股-纽交所',
        '153': '美股-粉单'
    }

    print(f"通过接口查询股票信息: 交易所代码: {market_code_map.get(market_code, f'未知({market_code})')}, 股票代码：{stock_code}, 数据条数: {data_limit}, K线类型: {kline_map.get(kline_type, f'未知({kline_type})')}, 复权类型: {response_format_map.get(response_format, f'未知({response_format})')} （数据来源 - 东方财富网）")

    url = "https://push2his.eastmoney.com/api/qt/stock/kline/get"
        
    params = {
        'fields1': 'f1,f2,f3,f4,f5,f6,f7,f8',
        'fields2': 'f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61,f62,f63,f64,f65',
        'ut': 'fa5fd1943c7b386f172d6893dbfba10c',
        'secid': f'{market_code}.{stock_code}',  # 股票代码：0.表示深市，1.表示沪市，601600是股票代码
        'dect': '1',
        'klt': f'{kline_type}',
        'lmt': f'{data_limit}',
        'fqt': f'{response_format}',
        'forcect': '1',
        'end': '20500000',
        'wbp2u': '1849325530509956|0|1|0|web',
        # 'cb': '__jp0' 临时注释，让接口直接返回 json 格式数据
    }

    cookies = {
        'qgqp_b_id': '2771b5010e8b63546b37537f5901f8a3',
        'st_si': '97744859410722',
        'st_nvi': 'AoAiV0o66FZ61fF-Yx_MSff95',
        'nid': '067ea514f3e6c054fe6b874dd91c5848',
        'nid_create_time': '1761208184117',
        'gvi': 'NBgCR2nqyAZANmxZfJOWC75c9',
        'gvi_create_time': '1761208184117',
        'fullscreengg': '1',
        'fullscreengg2': '1',
        'websitepoptg_api_time': '1761889337100',
        'st_pvi': '86220510526877',
        'st_asi': 'delete',
        'st_sp': '2025-10-23 16:29:43',
        'st_inirUrl': 'https://www.baidu.com/link',
        'st_sn': '12',
        'st_psi': '20251031134613879-118000300904-7890156815'
    }
        
    headers = {
        'Accept': '*/*',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Pragma': 'no-cache',
        'Referer': f'https://so.eastmoney.com/web/s?keyword={stock_code}',
        'Sec-Fetch-Dest': 'script',
        'Sec-Fetch-Mode': 'no-cors',
        'Sec-Fetch-Site': 'same-site',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
        'sec-ch-ua': '"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"'
    }
        
    response = requests.get(url=url, params=params, cookies=cookies, headers=headers, timeout=10) # 发送股票数据查询请求
    response.raise_for_status() # 检查响应状态码，失败直接抛出异常
        
    # 解析 k线 数据，追加到返回数组中
    ret: list[StockInfo] = [] # 返回股票信息列表
    response_data = json.loads(response.text)
    if response_data.get('rc', -1) == 0:
        data = response_data.get('data', {})
        for kline in data.get('klines', []):
            fields = kline.split(',')
            if date.today().strftime("%Y-%m-%d") != fields[0]: # 过滤掉今天的数据（因为当天数据可能会变）
                ret.append(
                    StockInfo(
                        market_code=market_code, 
                        stock_code=stock_code, 
                        name=data.get('name', ''),
                        date=fields[0],
                        open=float(fields[1]),
                        close=float(fields[2]),
                        high=float(fields[3]),
                        low=float(fields[4]),
                        volume=int(fields[5]),
                        amount=float(fields[6]),
                        amplitude=float(fields[7]),
                        change_rate=float(fields[8]),
                        change_amount=float(fields[9]),
                        turnover_rate=float(fields[10]),
                    )
                )

    return ret

def __query_stock_data(config: Config, market_code: str=1, stock_code: str="601600", data_limit: int = 58):
    """
    查询股票数据 - 从数据库中查询
    
    Args:
        config (Config): 配置信息
        market_code (str): 交易所代码
        stock_code (str): 股票代码
        data_limit (int): 查询数据条数

    Returns:
        pd.DataFrame: 股票数据
    """
    try:
        # 使用 SQLAlchemy 创建数据库连接（mysql-connector 驱动）
        engine = create_engine(f'mysql+mysqlconnector://{config.mysql["username"]}:{config.mysql["password"]}@{config.mysql["host"]}:{config.mysql["port"]}/{config.mysql["database"]}')

        # 创建会话（可选）
        Session = sessionmaker(bind=engine)
        session = Session()

        # 使用参数化查询
        query = text("SELECT * FROM stock_data WHERE market_code = :market_code AND stock_code = :stock_code ORDER BY id DESC LIMIT :limit")

        # 执行查询
        df = pd.read_sql(query, con=engine, params={
            "market_code": market_code,
            "stock_code": stock_code,
            "limit": data_limit
        })

        # 关闭会话
        session.close()
    except Exception as e:
        print(f"数据库查询错误: {e}")

    return df

def __insert_stock_data(config: Config, stock_list: list[StockInfo]):
    """
    插入股票数据 - 将股票数据插入到数据库中
    
    Args:
        config (Config): 配置信息
        stock_list (list[StockInfo]): 股票信息列表

    Returns:
        int: 插入的行数
    """
    try:
        engine = create_engine(f'mysql+mysqlconnector://{config.mysql["username"]}:{config.mysql["password"]}@{config.mysql["host"]}:{config.mysql["port"]}/{config.mysql["database"]}')

        sql = text("INSERT INTO stock_data (market_code, stock_code, name, date, open, close, high, low, volume, amount, amplitude, change_rate, change_amount, turnover_rate) VALUES (:market_code, :stock_code, :name, :date, :open, :close, :high, :low, :volume, :amount, :amplitude, :change_rate, :change_amount, :turnover_rate)")

        with engine.connect() as connection:
            # 准备所有数据
            data_list = []
            for stock in stock_list:
                data_list.append({
                    'market_code': stock.market_code,
                    'stock_code': stock.stock_code,
                    'name': stock.name,
                    'date': stock.date,
                    'open': stock.open,
                    'close': stock.close,
                    'high': f"{stock.high:.2f}",
                    'low': f"{stock.low:.2f}",
                    'volume': stock.volume,
                    'amount': stock.amount,
                    'amplitude': stock.amplitude,
                    'change_rate': stock.change_rate,
                    'change_amount': stock.change_amount,
                    'turnover_rate': stock.turnover_rate,
                })
            
            # 批量执行
            result = connection.execute(sql, data_list)
            connection.commit()
            return result.rowcount

    except Exception as e:
        print(f"数据库插入错误: {e}")

    return 0

def __clear_stock_data(config: Config, market_code: str=1, stock_code: str="601600"):
    """
    清除股票数据 - 将股票数据从数据库中删除
    
    Args:
        config (Config): 配置信息
        market_code (str): 交易所代码
        stock_code (str): 股票代码

    Returns:
        int: 删除的行数
    """
    try:
        engine = create_engine(f'mysql+mysqlconnector://{config.mysql["username"]}:{config.mysql["password"]}@{config.mysql["host"]}:{config.mysql["port"]}/{config.mysql["database"]}')

        sql = text("DELETE FROM stock_data WHERE market_code = :market_code AND stock_code = :stock_code")

        with engine.connect() as connection:
            # 准备所有数据
            result = connection.execute(sql, {
                'market_code': market_code,
                'stock_code': stock_code
            })
            connection.commit()
            return result.rowcount

    except Exception as e:
        print(f"数据库删除错误: {e}")

    return 0

def __stock_data(config: Config):
    """
    查询股票数据，并保存到数据库
    
    Args:
        config (Config): 配置信息

    Returns:
        bool: 是否查询成功
    """


    for stock_info in config.stock_list:
        print(f"=*=*=*=*=*=*= 交易所代码: {stock_info["market_code"]}, 股票代码：{stock_info["stock_code"]} =*=*=*=*=*=*=")

        to_remove = []  # 收集需要删除的元素
        should_insert_full_history = False # 是否插入所有数据标记
        needs_full_data_refresh = True # 数据库中是否有该股票数据（如果超过20天没有查过该股票数据，就将其设置为 True，这样就会插入所有数据）

        # 查询数据库中是否有该股票数据
        stock_list_by_db = __query_stock_data(config, stock_info["market_code"], stock_info["stock_code"], data_limit=20) # 从数据库中返回30条数据
        if stock_list_by_db.empty:
            # 如果没有，设置是否插入所有数据标记
            should_insert_full_history = True
            print(f"数据库中没有该股票数据，需要插入所有数据")
        else:
            # 如果有，调用接口查询最新的股票数据
            stock_list_by_interface = __search_stock_data(stock_info["market_code"], stock_info["stock_code"], data_limit='20', kline_type='101', response_format='0') # 从接口中返回30条数据
            for stock_interface in stock_list_by_interface:
                for index, stock_db in stock_list_by_db.iterrows():
                    if stock_db["date"] == f'{stock_interface.date}':
                        needs_full_data_refresh = False
                        if (f"{stock_db["high"]:.2f}" == f"{stock_interface.high:.2f}") and (f"{stock_db["low"]:.2f}" == f"{stock_interface.low:.2f}"):
                            # 如果数据库中的最高价和最低价与接口中的相同，移除接口中的该数据
                            to_remove.append(stock_interface) # 收集需要删除的元素，不立即删除
                        else:
                            # 如果数据库中的最高价和最低价与接口中的不同，删除数据库中的所有数据，并设置是否插入所有数据标记为 True
                            __clear_stock_data(config, stock_info["market_code"], stock_info["stock_code"]) # 清空这只股票的所有数据
                            should_insert_full_history = True
                            print(f"数据库中的数据与接口中的数据不同，需要清空后重新插入所有数据（股票有分红）")
                        
                        break

                # 如果需要插入所有数据，也就不用继续循环了，直接跳出
                if should_insert_full_history:
                    break

        # 循环结束后统一删除
        for item in to_remove:
            stock_list_by_interface.remove(item)

        if needs_full_data_refresh:
            # 清空这只股票的所有数据
            __clear_stock_data(config, stock_info["market_code"], stock_info["stock_code"])

        if should_insert_full_history:
            # 是否插入所有数据标记为 True。调用接口查询100年的股票数据，并插入数据库
            stock_list = __search_stock_data(stock_info["market_code"], stock_info["stock_code"], data_limit='36500', kline_type='101', response_format='0')
            if len(stock_list) == 0:
                raise Exception(f"接口返回股票数据为空，可能 交易所代码 或 股票代码 错误。交易所代码: {stock_info["market_code"]}，股票代码: {stock_info["stock_code"]} ")
            else:
                row_count = __insert_stock_data(config, stock_list)
                print(f"股票名称: {stock_list[0].name}") 
                print(f"插入所有数据: {row_count} 条") 
        else:
            # 如果不需要插入所有数据，就将接口中的数据插入数据库
            if stock_list_by_interface and len(stock_list_by_interface) > 0:
                row_count = __insert_stock_data(config, stock_list_by_interface)
                print(f"股票名称: {stock_list_by_interface[0].name}") 
                print(f"插入部分数据: {row_count} 条") 
            else:
                print(f"股票名称: {stock_list_by_db.iloc[0].get('name')}")
                print(f"数据库中已是最新数据，无需更新")

    return True


if __name__ == "__main__":
    config = Config()
    print(config)

    __stock_data(config)
